#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <GL/glut.h>
#include <vector>
#include "pixelMap.h"

using namespace std;

void controlDisplay (void);
void pictureDisplay (void);
void controlInit (void);
void pictureInit (void);
void keyboard(unsigned char key, int x, int y);
void mouse(int button, int state, int x, int y);
void motionMouse(int x, int y);
void specialKeys(int key, int x, int y);

void drawGrid();
void initControlPoints();
void drawControlPoints();
void drawCPCurve();
void recalcPixels();
void initCVector();
void updatePixels(int xValue, uByte yValue);

typedef struct _PixelPos
{
    int x, y;
}PixelPos;

int winMain, winControl;
int height, width;
float controlAlpha = 0.9;

PixelMap* pixelMap;
PixelPos cPoints[5];
bool moving = false;
int movingIdx = -1;
vector<PixelPos> colorVector[256];

pixel **pixels;

int main(int argc, char** argv)
{
    cout << "Escolha a imagem a ser carregada: ";
    char imagem[30];
    cin >> imagem;

    pixelMap = new PixelMap(imagem);
    height = pixelMap->GetHeight();
    width = pixelMap->GetWidth();
    glutInit(&argc, argv);

    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(width, height);
    glutInitWindowPosition (100, 100);
    winMain = glutCreateWindow ("Processamento de Imagens");
    glutDisplayFunc(pictureDisplay);
    pictureInit();

    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_ALPHA);
    glutInitWindowSize(256, 257);
    glutInitWindowPosition (height - 256, width - 257);
    winControl = glutCreateWindow("Control Window");
    glutMouseFunc( mouse );
    glutMotionFunc( motionMouse );
    glutDisplayFunc( controlDisplay );
    controlInit();

    initCVector();
    glutMainLoop();

     //Desalocando pixels
    for (int i = 0; i < pixelMap->GetHeight(); i++)
    {
        free(pixels[i]);
    }
    free(pixels);

    return 0;
}

void mouse(int button, int state, int x, int y)
{
    if (button == GLUT_LEFT_BUTTON)
    {
        if (state == GLUT_DOWN)
        {
            if (!moving)
            {
                for (int i = 0; i < 5; i++)
                {
                    float xInf = cPoints[i].x - 5;
                    float xSup = cPoints[i].x + 5;
                    float yInf = cPoints[i].y - 5;
                    float ySup = cPoints[i].y + 5;

                    if ((x > xInf) && (x < xSup) && ( 255 - y > yInf) && ( 255 - y < ySup))
                    {
                        moving = true;
                        movingIdx = i;
                        break;
                    }
                }
            }
        }
        if (state == GLUT_UP)
        {
            if (moving)
            {
//                recalcPixels();
                moving = false;
                movingIdx = -1;
            }
        }
    }
    glutPostRedisplay();
}

void motionMouse(int x, int y)
{
    if (moving)
    {
        if ((movingIdx != 0) && (movingIdx != 4) && (x > cPoints[movingIdx - 1].x) && (x < cPoints[movingIdx + 1].x))
        {
            cPoints[movingIdx].x = x;
            if (y >= 0 && y <= 255)
            {
                cPoints[movingIdx].y = 255 - y;
            }
            else if (y < 0)
            {
                cPoints[movingIdx].y = 255;
            }
            else
            {
                cPoints[movingIdx].y = 0;
            }
            recalcPixels();
        }
        else if ((movingIdx == 4) || (movingIdx == 0))
        {
            if (y >= 0 && y <= 255)
            {
                cPoints[movingIdx].y = 255 - y;
            }
            else if (y < 0)
            {
                cPoints[movingIdx].y = 255;
            }
            else
            {
                cPoints[movingIdx].y = 0;
            }
            recalcPixels();
        }
        glutPostRedisplay();
    }
}

void controlDisplay(void)
{
    glClear (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    // Desenhar a grade
    drawGrid();
    // Desenhar pontos
    drawCPCurve();
    drawControlPoints();

    glutSwapBuffers ();
    glutPostRedisplay();
}

void pictureDisplay(void)
{
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    pixelMap->ViewImage();

    glutSwapBuffers ();
    glutPostRedisplay();
}

void controlInit (void)
{
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glClearColor(0.0, 0.0, 0.0, controlAlpha);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0.0, 255.0, 0.0, 255.0, -1.0, 1.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    initControlPoints();
}

void pictureInit (void)
{
    // selecionar cor de fundo (preto)
    glClearColor( 0.0, 0.0, 0.0, 0.0 );
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    glOrtho( 0.0, width, 0.0, height, -1.0, 1.0 );

    glMatrixMode(GL_MODELVIEW);				// Select The Modelview Matrix
    glLoadIdentity();							// Inicializa com matriz identidade

    pixelMap->ConvertToGrayScale();
}

void drawGrid()
{


    glBegin(GL_QUADS);
	glColor4f(0.0, 0.0, 0.0, 0.7);
	glVertex3f(0,0,0);
	glVertex3f(10, 0,0);
	glColor4f(1.0, 1.0, 1.0, 0.7);
	glVertex3f(10,255,0);
	glVertex3f(0,255,0);
    glEnd();

    glBegin(GL_QUADS);
	glColor4f(0.0, 0.0, 0.0, 0.7);
	glVertex3f(0,0,0);
	glVertex3f(0, 10,0);
	glColor4f(1.0, 1.0, 1.0, 0.7);
	glVertex3f(255,10,0);
	glVertex3f(255,0,0);
    glEnd();

    glColor4f( 1.0, 1.0, 1.0, controlAlpha );
    glLineStipple(1, 0xAAAA);
    glEnable(GL_LINE_STIPPLE);

    glBegin(GL_LINES);
    for (int i = 0; i < 4; i++)
    {
        glVertex2f(0.25*i*255, 0.0);
        glVertex2f(0.25*i*255, 255.0);

        glVertex2f(0, 0.25*i*255);
        glVertex2f(255.0, 0.25*i*255);
    }
    glEnd();
    glDisable(GL_LINE_STIPPLE);

}

void initControlPoints()
{
    for (int i = 0; i < 5; i++)
    {
        cPoints[i].x = i*0.25*255;
        cPoints[i].y = i*0.25*255;
    }
}

void drawControlPoints()
{
    glColor3f(0.0, 0.7, 0.0);
    glPointSize(8.0);

    glBegin(GL_POINTS);
    {
        for (int i = 0; i < 5; i++)
        {
            glVertex2f(cPoints[i].x, cPoints[i].y);
        }
    }
    glEnd();
}

void drawCPCurve()
{
    glColor4f (0.0, 1.0, 0.0, controlAlpha);
    glLineStipple(1, 0x00FF);
    glEnable(GL_LINE_STIPPLE);

    glBegin(GL_LINE_STRIP);
    for (int i = 0; i < 5; i++)
    {
        glVertex2f(cPoints[i].x, cPoints[i].y);
    }
    glEnd();
    glDisable(GL_LINE_STIPPLE);
}

void recalcPixels()
{
    if (movingIdx == 0)
    {
        int iniX, iniY, destX, destY, dx;

        iniX = cPoints[movingIdx].x;
        iniY = cPoints[movingIdx].y;
        destX = cPoints[movingIdx + 1].x;
        destY = cPoints[movingIdx + 1].y;
        dx = destX - iniX;

        for (int i = 0; i < dx; i++)
        {
            int finalY;
            finalY = (int)(((i * (destY - iniY))/dx) + iniY);
            updatePixels( iniX + i, finalY );
        }
    }
    else if (movingIdx == 4)
    {
        int iniX, iniY, destX, destY, dx;

            iniX = cPoints[movingIdx - 1].x;
            iniY = cPoints[movingIdx - 1].y;
            destX = cPoints[movingIdx].x;
            destY = cPoints[movingIdx].y;
            dx = destX - iniX;

            for (int i = 0; i < dx; i++)
            {
                int finalY;
                finalY = (int)(((i * (destY - iniY))/dx) + iniY);
                updatePixels( iniX + i, finalY );
            }
    }
    else
    {
        int flag = 0;
        int iniX, iniY, destX, destY, dx;
        do
        {
            iniX = cPoints[movingIdx - 1 + flag].x;
            iniY = cPoints[movingIdx - 1 + flag].y;
            destX = cPoints[movingIdx + flag].x;
            destY = cPoints[movingIdx + flag].y;
            dx = destX - iniX;

            for (int i = 0; i < dx; i++)
            {
                int finalY;
                finalY = (int)(((i * (destY - iniY))/dx) + iniY);
                updatePixels( iniX + i, finalY );
            }
            flag++;
        }
        while (flag < 2);
    }
    pixelMap->ConvertPixelsToData(pixels);
    pixelMap->ViewImage();
}


void updatePixels(int xValue, uByte yValue)
{
        for(int j = 0; j < colorVector[xValue].size(); j++)
        {
            PixelPos altPixel = colorVector[xValue].at(j);
            pixels[altPixel.x][altPixel.y].value = (uByte)yValue;\
        }
}


void initCVector()
{
    pixels = (pixel **) malloc(pixelMap->GetHeight() * sizeof(pixel *));
    for (int i = 0; i < pixelMap->GetHeight(); i++)
    {
        pixels[i] = (pixel *) malloc(pixelMap->GetWidth() * sizeof(pixel));
    }

    pixelMap->ConvertDataToPixels(pixels);

    for (int i = 0; i < pixelMap->GetHeight(); i++){
        for(int j = 0; j < pixelMap->GetWidth(); j++)
        {
            int pos = pixels[i][j].value;
            PixelPos pixelPos;
            pixelPos.x = i;
            pixelPos.y = j;
            colorVector[pos].push_back(pixelPos);
        }
    }
}
